1. 静态链接重定位
-
什么是链接重定位?
-
链接时候进行符号表地址重定位,因为编译器无法去全局照顾每一个符号,尤其是模块间的函数调用
-
举例:函数调用
-
-
对编译期间的 main.o进行反汇编(objdump),查看汇编代码可以发现:main里针对函数my_test的符号解析 填充的其实是一个错误值,是不对的。如下
-
链接完成之后呢?再次针对main进行反汇编(objdump),就会发现,链接时候已经做了填充,如下:
2. 动态连接技术
-
举个例子先
-
main.c 需要链接lib1.so执行。首先编译(.o),链接(ELF),反汇编(objdump),ELF查看(readelf)
-
main.c如下
-
-
-
1.c如下
-
编译
-
将1.c编译成动态库,将main编译成ELF并链接该动态库 lib1.so
-
gcc -shared -fPIC 1.c -o lib1.so -g
-
gcc main.c -L. -l1 -o main -g
-
-
-
用objdump -S main.o > main.asm, 查看 main.asm可以看到main里调用 g_func 的汇编处,如下图
-
动态加载流程
-
动态加载基本流程
-
-
ELF Header内容(readelf -h main/readelf -l main)
-
动态链接技术的出现,解决了静态库耦合依赖,以及浪费内存问题。使得多个进程共享一个动态库成为可能。 在一定程度上节省了内存。延迟绑定(Lazy building)以及地址无关技术(PIC position-Independent-Code )的运用,让这个优势更加明显。
3. 技术简介
-
动态链接技术说白了就是解决了一个重定位问题。把程序原本链接时候符号重定位变成了运行时候动态重定位。 是如何做的呢?
-
其实就是延迟绑定。存放函数地址的数据表,称为全局偏移表(GOT, Global Offset Table),而那个额外代码段表, 称为程序链接表(延迟绑定技术)(PLT,Procedure Link Table)。
-
一切的一切,都是这两个表的杰作: PLT 和 GOT
-
先用官方枯燥无味的话大概总结下这个过程
TIP:
当需要动态链接时,内核会引导动态链接(ELF 解释器),该链接首先会初始化自身,然后加载指定的共享对象(已加载则不必)。接着它会执行必要的再定位,包括目标共享对象所使用的共享对象。LD_LIBRARY_PATH 环境变量定义查找可用共享对象的位置。定义完成后,控制权会被传回到初始程序以开始执行。 重定位是通过一个称为 GOT和PLT的间接机制来处理的。这些表格提供了 ld-linux.so 在再定位过程中加载的外部函数和数据的地址。这意味着无需改动需要间接机制(即,使用这些表格)的代码:只需要调整这些表格。一旦进行加载,或者只要需要给定的函数,就可以发生再定位(稍候在 用 Linux 进行动态加载 小节中会看到更多的差别)。 再定位完成后,动态链接器就会允许任何加载的共享程序来执行可选的初始化代码。该函数允许库来初始化内部数据并备之待用。这个代码是在上述 ELF 映像的 .init 部分中 定义的。在卸载库时,它还可以调用一个终止函数(定义为映像的 .fini 部分)。当初始化函数被调用时,动态链接器会把控制权转让给加载的原始映像。
-
下边会用更加人性化的方法分析下这个过程
4. PLT和GOT详解
4.1. 重定位
-
符号重定位,本质上是要解决当前编译单元如何访问“外部”符号这个问题。
-
ELF头部信息:
-
-
对于可执行文件来说,符号重定位是在链接阶段完成的。但是动态库是在运行时候才加载进来 所以,对于动态库里符号的重定位,只能推迟。称作动态加载(Dynamic Loading,DL)
-
redaelf 命令,可以查看ELF(Executable and Linking Format,ELF)文件
-
readelf -r 查看重定位段情况
-
readelf -d 可以看到程序所需动态库
-
-
链接时符号重定位举例
-
代码1.c 和 main.c
-
action direction [log] [quick] on interface [af] [proto protocol] \ from src_addr [port src_port] to dst_addr [port dst_port] \ [tcp_flags] [state]
以上是自己的一点总结